/**
 /**
 * @BelongsProject: Image_Recognition
 * @Author: 小蓝
 * @My Gitee URL: https://gitee.com/Shadows1946397262
 * @CreatingTime: 2023--22 13:33
 * @Description: A project with simple image recognition capability
 * @Version: 1.0
 */


import image from '@ohos.multimedia.image';
import common from '@ohos.app.ability.common';

//所操作图片的尺寸(图片的宽高一致)
const OPERATION_SIZE = 100

/*
 * 函数名: Get_NumberType_Array_MinValue_Index
 * 描述: 返回输入的number型Array中数值最小的元素所在索引
 */
function Get_NumberType_Array_MinValue_Index(arr:Array<number>):number{

  let location:number = 0

  for(var i = 0; i < arr.length; ++i){
    if(arr[i]<arr[location]){
      location = i
    }
  }

  return location

}

//最近邻分类器能力接口
interface I_Nearest_Neighbor_Classifier{

  train(context:common.Context,Train_Data:Array<string>)

  identify(test_data:string)

}

/*
 * 类名: XL_Image_NNC
 * 描述: 基于最近邻分类器的图像识别模块
 */
class XL_Image_NNC implements I_Nearest_Neighbor_Classifier{

  //日志标签
  private TAG:string = '------[XL_Image_NNC] '

  //BGRA_8888图对应向量的规模
  private dim_rgb:number = 4*(OPERATION_SIZE**2)

  //灰度图对应向量的规模
  private dim_gray:number = OPERATION_SIZE**2

  //RGB图向量转化为灰度图向量时, 三原色(red green blue)通道值各自占灰度值的权重, 并且满足R_Weight + G_Weight + B_Weight = 1
  private R_Weight:number = 0.299
  private G_Weight:number = 0.587
  private B_Weight:number = 0.114

  //已加入的图像向量的集合(BGRA_8888)
  private Trained_Data_RGB:Array<Uint8Array> = []

  //已加入的图像向量的集合(灰度图)
  private Trained_Data_Gray:Array<Uint8Array> = []

  //存储距离(欧式范数)的数组
  private Distance_Array:Array<number> = []

  //储存资源管理模块的变量
  private resourceMgr = null

  /*
   * 方法名: train
   * 描述: 为分类器填充数据, 使得分类器获取监督模式识别的功能
   * 参数: context: UIAbility的上下文对象  Train_Data: 待训练的图片集（图片需要提前储存在rawfile目录下）
   */
  public async train(context:common.Context,Train_Data:Array<string>) {

    //通过context获取ResourceManager(资源管理模块)
    this.resourceMgr = context.resourceManager

    //遍历和处理待输入的图片数据
    for (var item of Train_Data) {

      //通过resourceMgr的getRawFileContent方法(填入图片的文件名字符串),获取rawfile目录下某个图片所资源对应的UintArray
      let rawData = await this.resourceMgr.getRawFileContent(item)

      //通过先前获取的UintArray创建ImageSource实例
      let imageSource = image.createImageSource(rawData.buffer)

      //通过ImageSource实例创建像素表
      let pixelMap = await imageSource.createPixelMap()

      //将像素表读取到新建的ArrayBuffer变量中
      let buffer = new ArrayBuffer(this.dim_rgb)
      await pixelMap.readPixelsToBuffer(buffer)
        .catch(err => {
          console.error(this.TAG+`err: +${err}`)
        })

      //最后将ArrayBuffer携带的RGB型图像向量存入Trained_Data_RGB（向量集合）中
      this.Trained_Data_RGB.push(new Uint8Array(buffer))

    }

    //将获取的RGB型图像向量集合转化为灰度型图像向量集合
    for(var element of this.Trained_Data_RGB){

      let GrayScaleVector = new Uint8Array(this.dim_gray)
      let index:number = 0

      //遍历RGB型图像向量的元素
      for(var i = 0; i < element.length; i++){
        if((i+1)%4 == 0){

          //获取像素点的R,G,B通道值, 将他们加权求和得到灰度值
          var grayScale = this.R_Weight*element[i-3]+this.G_Weight*element[i-2]+this.B_Weight*element[i-1]

          //存储
          GrayScaleVector[index++] = grayScale
        }
      }

      //最后将GrayScaleVector携带的灰度型图像向量存入Trained_Data_Gray（向量集合）中
      this.Trained_Data_Gray.push(GrayScaleVector)

    }


  }

  /*
   * 方法名: identify
   * 描述: 基于已获取的数据，完成监督模式识别，返回输入样本的最近邻在Trained_Data_Gray中的索引
   * 参数: test_data: 待识别的图片（图片需要储存在rawfile目录下）
   */
  public async identify(test_data:string):Promise<number>{

    //排除异常情况
    if(this.resourceMgr == null){
      console.error(this.TAG+'Please train the image data before identifying')
      return -1
    }

    //获取rawfile目录下某个图片所对应的UintArray
    let rawData = await this.resourceMgr.getRawFileContent(test_data)

    //通过先前获取的UintArray创建ImageSource实例
    let imageSource = image.createImageSource(rawData.buffer)

    //通过ImageSource实例创建像素表
    let pixelMap = await imageSource.createPixelMap()

    //将像素表读取到新建的buffer变量中
    let buffer = new ArrayBuffer(this.dim_rgb)
    await pixelMap.readPixelsToBuffer(buffer)
      .catch(err => {
        console.error(this.TAG+`err: +${err}`)
      })

    let Sample_RGB = new Uint8Array(buffer)

    let Sample_Gray = new Uint8Array(this.dim_gray)

    let index:number = 0

    //将RGB型的图像向量转化为灰度型的图像向量
    for(var i = 0; i < Sample_RGB.length; i++){
      if((i+1)%4 == 0){
        var grayScale = this.R_Weight*Sample_RGB[i-3]+this.G_Weight*Sample_RGB[i-2]+this.B_Weight*Sample_RGB[i-1]
        Sample_Gray[index++] = grayScale
      }
    }

    //赋初值
    this.Distance_Array = []

    //计算待检测图像向量与每项已训练图片向量在高维空间的的距离（距离采用欧式范数）, 即(Σ(A[i] - B[i]))^0.5, i ∈ [0, dim_gray) ∩ N
    for(var item of this.Trained_Data_Gray){

      var distance:number = 0

      //计算dim_gray维向量空间上样本与已训练数据的欧式范数
      for(var i = 0; i < this.dim_gray; i++){
        distance += (Sample_Gray[i]-item[i])**2
      }
      distance = distance**0.5

      this.Distance_Array.push(distance)
      console.info(this.TAG+'distance: '+distance)

    }

    //获取Distance_Array中最小元素所在索引并输出, 此索引即为样本的最近邻在Trained_Data_Gray中的索引
    return Get_NumberType_Array_MinValue_Index(this.Distance_Array)

  }

}

//导出本模块
export default new XL_Image_NNC()